;;; QGn.m generator for SAT (adapted from code due to Mark Stickel)
;;
;; All the usual disclaimers apply
;;
;; Clauses are returned using the trie data-structure.
;; to return a more list based representation, use trie2clause. 
;; For example, (trie2clause (qg2 5)) returns clauses representing
;; an idempotent version of QG2.5. As a second example, 
;; (trie2clause (qg5 7 :not-necessarily-idempotent T) returns
;; clauses representing QG5.7 in which idempotency is not required. 

(defun trie2clause (tries)
   (cons 'and (trie2clause2 (dp-clauses tries))))

(defun trie2clause2 (tries)
   (cond ((null tries) nil)
         (t (cons (cons 'or (onetrie2oneclause (car tries)))
                  (trie2clause2 (cdr tries))))))

(defun onetrie2oneclause (trie)
   (cond ((null trie) nil)
         ((> (car trie) 0) (cons (car trie) (onetrie2oneclause (cdr trie))))
         (t (cons (list 'not (- (car trie))) (onetrie2oneclause (cdr trie))))))


#|
;; this version can be used to create variables from 1 to n^3 without gaps
(defun encode-qg-atom (n i j k &optional (number 0))
  (+ (* (+ (* (1- i) n) (1- j)) n) k))
|#

(defun encode-qg-atom (n i j k &optional (number 0))
  (+ n (* number 25) (* 100 (+ (* (+ (* (1- i) n) (1- j)) n) (1- k)))))

(defun decode-qg-atom (v)
  (let ((n (mod v 25))
	(sym (cdr (assoc (floor (mod v 100) 25) '((0 . *) (1 . *1) (2 . *2) (3 . *3)))))
	i j k)
    (setq v (floor v 100))
    (setq k (1+ (mod v n)))
    (setq v (floor v n))
    (setq j (1+ (mod v n)))
    (setq v (floor v n))
    (setq i (1+ v))
    (values `(= (,sym ,i ,j) ,k) n)))

(defvar use-row-and-column-surjectivity t)

(defun qg (v &key (number 0) not-necessarily-idempotent incomplete initial-values clause-set)
  (myassert (< v 25))
  (myassert (<= 0 number 3))
  (let ((v-n (if incomplete (- v incomplete) v)))
    (unless not-necessarily-idempotent
      (loop for i from 1 upto v-n
	    do (setq clause-set (dp-insert (list (encode-qg-atom v i i i number)) clause-set))))
    (UNLESS (> NUMBER 0)
      (loop for i from 1 upto v-n
	    do (loop for j from 1 upto v
		     when (< (1+ j) i)
		       do (setq clause-set (dp-insert (list (- (encode-qg-atom v i v j number))) clause-set)))))
    (loop for x in initial-values
	  do (setq clause-set (dp-insert (list (encode-qg-atom v (first x) (second x) (third x))) clause-set)))
    (loop for i from 1 upto v
	  do (loop for j from 1 upto v
		   unless (and (> i v-n) (> j v-n))
		     do (setq clause-set (dp-insert-sorted
					   (loop for k from 1 upto (if (or (> i v-n) (> j v-n)) v-n v)
						 collect (encode-qg-atom v i j k number))
					   clause-set))))
    (when use-row-and-column-surjectivity
      (loop for i from 1 upto v			;new clauses suggested by Fujita 2/24/93
	    do (loop for j from 1 upto v
		     unless (and (> i v-n) (> j v-n))
		       do (setq clause-set (dp-insert-sorted
					     (loop for k from 1 upto (if (or (> i v-n) (> j v-n)) v-n v)
						   collect (encode-qg-atom v i k j number))
					     clause-set))
			  (setq clause-set (dp-insert-sorted
					     (loop for k from 1 upto (if (or (> i v-n) (> j v-n)) v-n v)
						   collect (encode-qg-atom v k i j number))
					     clause-set)))))
    (loop for i from 1 upto v
	  do (loop for j from 1 upto v
		   do (loop for k1 from 1 upto v
			    do (loop for k2 from (1+ k1) upto v
				     do (unless (and (> i v-n) (> j v-n))
					  (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v i j k1 number))
										   (- (encode-qg-atom v i j k2 number)))
									     clause-set)))
					(unless (and (> i v-n) (or (> k1 v-n) (> k2 v-n)))
					  (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v i k1 j number))
										   (- (encode-qg-atom v i k2 j number)))
									     clause-set))
					  (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v k1 i j number))
										   (- (encode-qg-atom v k2 i j number)))
									     clause-set)))))))
    clause-set))

(defun qg1 (v &key not-necessarily-idempotent incomplete initial-values)
  (let ((v-n (if incomplete (- v incomplete) v))
	(clause-set (qg v :not-necessarily-idempotent not-necessarily-idempotent :incomplete incomplete :initial-values initial-values)))
    (loop for a from 1 upto v
	  do (loop for b from 1 upto v
		   unless (and (> a v-n) (> b v-n))
		     do (loop for c from 1 upto v
			      do (loop for d from 1 upto v
				       unless (and (> c v-n) (> d v-n))
					 unless (and (= a c) (= b d))
					   do (loop for ab from 1 upto v
						    do (loop for x from 1 upto v
							     unless (and (> x v-n) (or (> b v-n) (> d v-n)))
							       do (setq clause-set (dp-insert-sorted
										     (list (- (encode-qg-atom v a b ab))
											   (- (encode-qg-atom v c d ab))
											   (- (encode-qg-atom v x b a))
											   (- (encode-qg-atom v x d c)))
										     clause-set))))))))
    clause-set))

(defun qg2 (v &key not-necessarily-idempotent incomplete initial-values)
  (let ((v-n (if incomplete (- v incomplete) v))
	(clause-set (qg v :not-necessarily-idempotent not-necessarily-idempotent :incomplete incomplete :initial-values initial-values)))
    (loop for a from 1 upto v
	  do (loop for b from 1 upto v
		   unless (and (> a v-n) (> b v-n))
		     do (loop for c from 1 upto v
			      do (loop for d from 1 upto v
				       unless (and (> c v-n) (> d v-n))
					 unless (and (= a c) (= b d))
					   do (loop for ab from 1 upto v
						    do (loop for x from 1 upto v
							     unless (and (> x v-n) (or (> b v-n) (> d v-n)))
							       do (setq clause-set (dp-insert-sorted
										     (list (- (encode-qg-atom v a b ab))
											   (- (encode-qg-atom v c d ab))
											   (- (encode-qg-atom v b x a))
											   (- (encode-qg-atom v d x c)))
										     clause-set))))))))
    clause-set))

(defun qg3 (v &key not-necessarily-idempotent incomplete negative-equation-clauses initial-values)
  (let ((v-n (if incomplete (- v incomplete) v))
	(clause-set (qg v :not-necessarily-idempotent not-necessarily-idempotent :incomplete incomplete :initial-values initial-values)))
    (loop for a from 1 upto v
	  do (loop for b from 1 upto v
		   unless (and (> a v-n) (> b v-n))
		     do (loop for ab from 1 upto v
			      do (loop for ba from 1 upto v
				       unless (and (> ab v-n) (> ba v-n))
					 do (if negative-equation-clauses
						(loop for u from 1 upto v
						      unless (= a u)
							do (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v a b ab))
												    (- (encode-qg-atom v b a ba))
												    (- (encode-qg-atom v ab ba u)))
											      clause-set)))
						(setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v a b ab))
											 (- (encode-qg-atom v b a ba))
											 (encode-qg-atom v ab ba a))
										   clause-set)))))))
    ;; Slaney: if a.ax=x or xa.a=x then a=x
    (loop for a from 1 upto v
	  do (loop for x from 1 upto v
		   unless (and (> a v-n) (> x v-n))
		     unless (= a x)
		       do (loop for u from 1 upto v
				unless (and (> a v-n) (> u v-n))
				  do (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v a x u))
									      (- (encode-qg-atom v a u x)))
									clause-set))
				     (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v x a u))
									      (- (encode-qg-atom v u a x)))
									clause-set)))))
    clause-set))

(defun qg4 (v &key not-necessarily-idempotent incomplete negative-equation-clauses initial-values)
  (let ((v-n (if incomplete (- v incomplete) v))
	(clause-set (qg v :not-necessarily-idempotent not-necessarily-idempotent :incomplete incomplete :initial-values initial-values)))
    (loop for a from 1 upto v
	  do (loop for b from 1 upto v
		   unless (and (> a v-n) (> b v-n))
		     do (loop for ab from 1 upto v
			      do (loop for ba from 1 upto v
				       unless (and (> ab v-n) (> ba v-n))
					 do (if negative-equation-clauses
						(loop for u from 1 upto v
						      unless (= a u)
							do (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v a b ab))
												    (- (encode-qg-atom v b a ba))
												    (- (encode-qg-atom v ba ab u)))
											      clause-set)))
						(setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v a b ab))
											 (- (encode-qg-atom v b a ba))
											 (encode-qg-atom v ba ab a))
										   clause-set)))))))
    clause-set))

(defun qg5 (v &key not-necessarily-idempotent incomplete negative-equation-clauses no-extra-equation-clauses initial-values)
  (let ((v-n (if incomplete (- v incomplete) v))
	(clause-set (qg v :not-necessarily-idempotent not-necessarily-idempotent :incomplete incomplete :initial-values initial-values)))
    (loop for a from 1 upto v
	  do (loop for b from 1 upto v
		   unless (and (> b v-n) (> a v-n))
		     do (loop for ba from 1 upto v
			      unless (and (> ba v-n) (> b v-n))
				do (loop for ba.b from 1 upto v
					 unless (and (> ba.b v-n) (> b v-n))
					   do (if negative-equation-clauses
						  (loop for u from 1 upto v
							unless (= a u)
							  do (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v b a ba))
												      (- (encode-qg-atom v ba b ba.b))
												      (- (encode-qg-atom v ba.b b u)))
												clause-set)))
						  (progn
						    (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v b a ba))
											     (- (encode-qg-atom v ba b ba.b))
											     (encode-qg-atom v ba.b b a))
										       clause-set))
						    ;; suggested by Fujita
						    ;; also Slaney's y(xy.y)=x and (y.xy)y=x (6/28/93)
						    (UNLESS NO-EXTRA-EQUATION-CLAUSES	;these are valid
						      (SETQ CLAUSE-SET (DP-INSERT-SORTED (LIST (ENCODE-QG-ATOM V B A BA)
											       (- (ENCODE-QG-ATOM V BA B BA.B))
											       (- (ENCODE-QG-ATOM V BA.B B A)))
											 CLAUSE-SET))
						      (SETQ CLAUSE-SET (DP-INSERT-SORTED (LIST (- (ENCODE-QG-ATOM V B A BA))
											       (ENCODE-QG-ATOM V BA B BA.B)
											       (- (ENCODE-QG-ATOM V BA.B B A)))
											 CLAUSE-SET)))))))))
    clause-set))

(defun qg6 (v &key incomplete negative-equation-clauses initial-values)
  (let ((v-n (if incomplete (- v incomplete) v))
	(clause-set (qg v :incomplete incomplete :initial-values initial-values)))
    (if negative-equation-clauses
	(loop for ab.b from 1 upto v
	      do (loop for a.ab from 1 upto v
		       unless (= ab.b a.ab)
			 do (loop for a from 1 upto v
				  do (loop for b from 1 upto v
					   unless (and (> a v-n) (> b v-n))
					     do (loop for ab from 1 upto v
						      unless (and (> ab v-n) (or (> a v-n) (> b v-n)))
							do (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v a b ab))
												    (- (encode-qg-atom v ab b ab.b))
												    (- (encode-qg-atom v a ab a.ab)))
											      clause-set)))))))
	(loop for x from 1 upto v
	      do (loop for a from 1 upto v
		       do (loop for b from 1 upto v
				unless (and (> a v-n) (> b v-n))
				  do (loop for ab from 1 upto v
					   unless (and (> ab v-n) (or (> a v-n) (> b v-n)))
					     do (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v a b ab))
											 (- (encode-qg-atom v ab b x))
											 (encode-qg-atom v a ab x))
										   clause-set))
						(setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v a b ab))
											 (- (encode-qg-atom v a ab x))
											 (encode-qg-atom v ab b x))
										   clause-set)))))))
    clause-set))

(defun qg7 (v &key incomplete negative-equation-clauses MORE-EQUATION-CLAUSES initial-values)
  (let ((v-n (if incomplete (- v incomplete) v))
	(clause-set (qg v :incomplete incomplete :initial-values initial-values)))
    (if negative-equation-clauses
	(loop for ba.b from 1 upto v
	      do (loop for a.ba from 1 upto v
		       unless (= ba.b a.ba)
			 do (loop for a from 1 upto v
				  do (loop for b from 1 upto v
					   unless (and (> a v-n) (> b v-n))
					     do (loop for ba from 1 upto v
						      unless (and (> ba v-n) (or (> a v-n) (> b v-n)))
							do (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v b a ba))
												    (- (encode-qg-atom v ba b ba.b))
												    (- (encode-qg-atom v a ba a.ba)))
											      clause-set)))))))
	(loop for x from 1 upto v
	      do (loop for a from 1 upto v
		       do (loop for b from 1 upto v
				unless (and (> a v-n) (> b v-n))
				  do (loop for ba from 1 upto v
					   unless (and (> ba v-n) (or (> a v-n) (> b v-n)))
					     do (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v b a ba))
											 (- (encode-qg-atom v ba b x))
											 (encode-qg-atom v a ba x))
										   clause-set))
						(setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v b a ba))
											 (- (encode-qg-atom v a ba x))
											 (encode-qg-atom v ba b x))
										   clause-set))
						(WHEN MORE-EQUATION-CLAUSES	;valid if zb=az has unique solution for z
						  (SETQ CLAUSE-SET (DP-INSERT-SORTED (LIST (ENCODE-QG-ATOM V B A BA)
											   (- (ENCODE-QG-ATOM V BA B X))
											   (- (ENCODE-QG-ATOM V A BA X)))
										     CLAUSE-SET))))))))
    clause-set))

(defun qg7a (v &key incomplete initial-values)
  (let ((v-n (if incomplete (- v incomplete) v))
	(clause-set (qg v :incomplete incomplete :initial-values initial-values)))
    ;;finds (132) conjugate equivalents, as suggested by Slaney
    ;;note: constraint on rightmost column not translated to conjugate equivalent, so number of models differs from qg7
    ;; (xy.x)y=x
    (loop for x from 1 upto v
	  do (loop for y from 1 upto v
		   unless (and (> x v-n) (> y v-n))
		     do (loop for xy from 1 upto v
			      unless (and (> xy v-n) (> x v-n))
				do (loop for xy.x from 1 upto v
					 unless (and (> xy.x v-n) (> y v-n))
					   do (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v x y xy))
										       (- (encode-qg-atom v xy x xy.x))
										       (encode-qg-atom v xy.x y x))
										 clause-set))))))
    ;; (xy.y).xy=x
    (loop for x from 1 upto v
	  do (loop for y from 1 upto v
		   unless (and (> x v-n) (> y v-n))
		     do (loop for xy from 1 upto v
			      unless (and (> xy v-n) (> y v-n))
				do (loop for xy.y from 1 upto v
					 unless (and (> xy.y v-n) (> xy v-n))
					   do (setq clause-set (dp-insert-sorted (list (- (encode-qg-atom v x y xy))
										       (- (encode-qg-atom v xy y xy.y))
										       (encode-qg-atom v xy.y xy x))
										 clause-set))))))
    clause-set))

(defun quasigroup-problem (problem order &rest other-args &key find-one-model INITIAL-CLAUSES &allow-other-keys)
  (let (clause-set x1)
    (setq problem (or (cdr (assoc problem '((1 . qg1) (2 . qg2) (3 . qg3) (4 . qg4) (5 . qg5) (6 . qg6) (7 . qg7)
					    (7a . qg7a))))
		      problem))
    (print (list* 'begin 'problem problem order other-args))
    (setq other-args (loop for x = other-args then (cddr x)
			   until (null x)
			   unless (member (first x) '(:find-one-model :INITIAL-CLAUSES))
			     nconc (list (first x) (second x))))
    (time (setq clause-set (apply problem order other-args)))
    (LOOP FOR CLAUSE IN INITIAL-CLAUSES
	  DO (SETQ CLAUSE-SET (DP-INSERT-SORTED (MAPCAR #'(LAMBDA (X) (ENCODE-QG-ATOM ORDER (FIRST X) (SECOND X) (THIRD X))) CLAUSE)
						CLAUSE-SET)))
;;  (dp-count clause-set t)
    (time (setq x1 (dp-satisfiable-p clause-set (not find-one-model))))
    (when x1
      (print x1)
      (print-quasigroup-multiplication-table (make-quasigroup-multiplication-table (if find-one-model x1 (car x1))))
      (loop for model in (if find-one-model (list x1) x1)
	    do (myassert2 (verify-quasigroup-multiplication-table (make-quasigroup-multiplication-table model) problem))))
    (print (list* 'end 'problem problem order other-args))
    nil))

(defun quasigroup-problem-file (file problem order &rest other-args)
  (let (clause-set)
    (setq problem (or (cdr (assoc problem '((1 . qg1) (2 . qg2) (3 . qg3) (4 . qg4) (5 . qg5) (6 . qg6) (7 . qg7)))) problem))
    (setq clause-set (apply problem order other-args))
    (dp-count clause-set t)
    (with-open-file (s file :direction :output)
      (mapc #'(lambda (x) (prin1 x s) (terpri s)) (dp-clauses clause-set)))
    nil))

(defun make-quasigroup-multiplication-table (model)
  (multiple-value-bind (x n)
      (decode-qg-atom (car model))
    (declare (ignore x))
    (loop with table = (make-array (list (1+ n) (1+ n)) :initial-element nil)
	  for v in model
	  as atom = (decode-qg-atom v)
	  as i = (cadadr atom)
	  as j = (car (cddadr atom))
	  as k = (caddr atom)
	  IF (NOT (EQ (CAADR ATOM) '*))		;ONLY CONSTRUCT FIRST TABLE
	    DO NIL
	  else
	    if (null (aref table i j))
	      do (setf (aref table i j) k)
	  else
	    do (error "~D*~D=~D and ~D*~D=~D" i j (aref table i j) i j k)
	  finally (return table))))

(defun print-quasigroup-multiplication-table (table)
  (let ((n (1- (car (array-dimensions table)))))
    (format t "~%x*y  y")
    (loop for j from 1 upto n do (format t "~4D" j))
    (format t "~%   x +")
    (loop repeat n do (format t "----"))
    (loop for i from 1 upto n
	  do (format t "~%~4D |" i)
	     (loop for j from 1 upto n
		   as v = (aref table i j)
		   do (if v (format t "~4D" v) (format t "   -"))))
;;  (format t "~%")
    table))

(defun verify-quasigroup-multiplication-table (table &optional type not-necessarily-idempotent)
  (let ((n (1- (car (array-dimensions table)))))
    (and
      (loop for i from 1 upto n
	    always (loop for j from 1 upto n
			 always (and (integerp (aref table i j)) (<= 1 (aref table i j) n))))
      (or
	not-necessarily-idempotent
	(loop for i from 1 upto n
	      always (= (aref table i i) i)))
      (loop for i from 1 upto n
	    always (loop for k from 1 upto n
			 always (= (loop for j from 1 upto n count (= (aref table i j) k)) 1)))
      (loop for j from 1 upto n
	    always (loop for k from 1 upto n
			 always (= (loop for i from 1 upto n count (= (aref table i j) k)) 1)))
      (or
	(null type)
	(case type
	  ((132 213 231)
	   (qg-orthogonal-p table (qg-conjugate table type)))
	  ((qg1 qg1a 1 321)
	   (qg-orthogonal-p table (qg-conjugate table 321)))
	  ((qg2 qg2a 2 312)
	   (qg-orthogonal-p table (qg-conjugate table 312)))
	  ((qg3 3)
	   (loop for x from 1 upto n
		  always (loop for y from 1 upto n
			       always (= (aref table (aref table x y) (aref table y x)) x))))
	  ((qg4 4)
	    (loop for x from 1 upto n
		  always (loop for y from 1 upto n
			       always (= (aref table (aref table y x) (aref table x y)) x))))
	  ((qg5 5)
	    (loop for x from 1 upto n
		  always (loop for y from 1 upto n
			       always (= (aref table (aref table (aref table y x) y) y) x))))
	  ((qg6 6)
	    (loop for x from 1 upto n
		  always (loop for y from 1 upto n
			       always (= (aref table (aref table x y) y) (aref table x (aref table x y))))))
	  ((qg7 7)
	    (loop for x from 1 upto n
		  always (loop for y from 1 upto n
			       always (= (aref table (aref table y x) y) (aref table x (aref table y x))))))
	  ((qg7a 7a)
	    (let ((table (qg-conjugate table 132)))
	      (loop for x from 1 upto n
		    always (loop for y from 1 upto n
				 always (= (aref table (aref table y x) y) (aref table x (aref table y x)))))))
	  (otherwise
	    (format t "~%Don't know how to verify models of problem ~A." type)
	    t)))
      table)))

(defun qg-conjugate (table conjugate)
  (cond
    ((= conjugate 123)
     table)
    (t
     (loop with n = (1- (car (array-dimensions table)))
	   with table2 = (make-array (list (1+ n) (1+ n)))
	   for a from 1 upto n
	   do (loop for b from 1 upto n
		    as c = (aref table a b)
		    do (ecase conjugate
			 (132 (setf (aref table2 a c) b))
			 (213 (setf (aref table2 b a) c))
			 (231 (setf (aref table2 b c) a))
			 (312 (setf (aref table2 c a) b))
			 (321 (setf (aref table2 c b) a))))
	   finally (return table2)))))

(defun qg-orthogonal-p (table1 table2)
  (loop with n = (1- (car (array-dimensions table1)))
	for a from 1 upto n
	always (loop for b from 1 upto n
		     always (loop for c from 1 upto n
				  always (loop for d from 1 upto n
					       always (or (not (= (aref table1 a b) (aref table1 c d)))
							  (not (= (aref table2 a b) (aref table2 c d)))
							  (and (= a c) (= b d))))))))


(defun myassert2 (arg)
  (assert arg)
)


(defmacro EMPTY-TRIE-P (trie)
  ;; trie constains satisfiable empty set of clauses
  `(null ,trie))

(defmacro EMPTY-CLAUSE-TRIE-P (trie)
  ;; trie contains just the unsatisfiable empty clause
  `(eq ,trie t))


(defun DP-INSERT (clause clause-set)
  (trie-insert clause clause-set))

(defun DP-INSERT-SORTED (clause clause-set)
  (trie-insert-sorted clause clause-set))

(defun DP-INSERT-FILE (filename &optional clause-set)
  (when dp-print-summary
    (format t "~2%Problem from file ~A:" filename))
  (with-open-file (s filename :direction :input)
    (loop for clause = (read s nil 'eof)
	  until (eq clause 'eof)
	  if (consp clause)
	    do (setq clause-set (dp-insert-sorted clause clause-set))
	  else
	    do (warn "Skipping nonclause ~A in file." clause)
	  finally (return clause-set))))

(defun DP-COUNT (clause-set &optional print-p)
  ;; (dp-count clause-set) returns and optionally prints the clause and literal count
  ;; of clauses stored in clause-set
  (trie-count clause-set print-p))

(defun DP-CLAUSES (clause-set &optional decode-fun map-fun)
  ;; either return or apply map-fun to all clauses in clause-set
  (trie-clauses clause-set decode-fun map-fun))

(defun DP-CLAUSES-FILE (filename clause-set &optional decode-fun)
  ;; write clauses in clause-set to a file
  (with-open-file (s filename :direction :output :if-exists :new-version)
    (dp-clauses clause-set decode-fun #'(lambda (clause) (princ clause s) (terpri s))))
  nil)

;;; Propositional clause trie is of the form
;;;   t  for a clause with no more literals at this position in the trie
;;;   alist  contains pairs (called "tp" for "trie-pair")
;;;          of the form (atom . (trie1 . trie2))
;;;     atom   proposition symbol encoded as a positive number
;;;     trie1  subtrie for clauses here in trie containing atom positively
;;;     trie2  subtrie for clauses here in trie containing atom negatively
;;;   alist pairs are ordered by <
;;; This is a very efficient representation for sets of clauses
;;; that was suggested by Johan deKleer (AAAI92).
;;;
;;; Propositional clause tries are constructed by inserting clauses one-by-one
;;; by the trie-insert function
;;;   input for trie-insert: C v A v -B  =>  (1 -2 3)
;;;     where A is encoded as 1, B as 2, and C as 3
;;;   literals must be ordered by < on absolute value
;;;   trie-insert-sorted sorts the clause before inserting it
;;;
;;; Proposition symbols are assumed to be encoded as fixnums for greater efficiency.
;;; Nonfixnum (e.g., larger or noninteger) numbers can be used by eliminating the
;;; (typep atom 'fixnum) assertion in trie-insert* and replacing (the fixnum expr)
;;; by expr in trie-merge* and trie-assign-atoms*.

(defmacro MAKE-TP (atom subtries)
  `(cons ,atom ,subtries))

(defmacro TP-ATOM (tp)
  `(car ,tp))

(defmacro TP-SUBTRIES (tp)
  `(cdr ,tp))

(defmacro TRUE-SUBTRIE (subtries)
  `(car ,subtries))

(defmacro FALSE-SUBTRIE (subtries)
  `(cdr ,subtries))

(defun TRIE-INSERT-SORTED (clause trie)
  (trie-insert (sort-clause clause) trie))

(defun TRIE-INSERT (clause trie)
  ;; destructive insertion, but value must be used
  (myassert (not (null clause)))		;only nonempty clauses can be inserted
  (myassert (not (zerop (first clause))))	;don't allow 0, since -0 = 0 => (not atom) = atom
  (trie-insert* clause trie 0))

(defun TRIE-INSERT* (clause trie previous-literal)
  (myassert (<= (abs previous-literal) (abs (first clause))))	;literals in ascending order
  (cond
    ((empty-clause-trie-p trie)
     t)
    ((= previous-literal (first clause))	;ignore duplicate literal in clause
     (let ((l1 (rest clause)))
       (if (null l1) t (trie-insert* l1 trie previous-literal))))
    ((= previous-literal (- (first clause)))	;ignore clause with complementary literal
     trie)
    (t
     (let ((atom (abs (first clause))) v)
       (myassert (typep atom 'fixnum))
       (cond
	 ((or (null trie) (< atom (tp-atom (first trie))))
	  (setq trie (cons (make-tp atom (setq v (cons nil nil))) trie)))
	 ((= atom (tp-atom (first trie)))
	  (setq v (tp-subtries (first trie))))
	 (t
	  (loop for trie on trie
		when (or (null (rest trie)) (< atom (tp-atom (second trie))))
		  do (setf (rest trie) (cons (make-tp atom (setq v (cons nil nil))) (rest trie)))
		     (return nil)
		when (= atom (tp-atom (second trie)))
		  do (setq v (tp-subtries (second trie)))
		     (return nil))))
       (let ((l1 (rest clause)))
	 (cond
	   ((> (first clause) 0)
	    (setf (true-subtrie v) (if (null l1) t (trie-insert* l1 (true-subtrie v) (first clause)))))
	   (t
	    (setf (false-subtrie v) (if (null l1) t (trie-insert* l1 (false-subtrie v) (first clause)))))))
       trie))))

(defun SORT-CLAUSE (clause)
  ;; utility function returns < sorted variant of clause
  ;; to use for insertion into a trie by trie-insert
  (cond
    ((null clause)
     nil)
    ((null (rest clause))
     clause)
    ((null (rest (rest clause)))
     (if (< (abs (first clause)) (abs (second clause)))
	 clause
	 (list (second clause) (first clause))))
    (t
     (sort (copy-list clause) #'< :key #'abs))))

(defun TRIE-COUNT (trie &optional print-p)
  ;; (trie-count trie) returns and optionally prints the clause and literal count
  ;; of clauses stored in trie
  (let ((nclauses 0) (nliterals 0) (nnodes 0))
    (labels
      ((trie-count* (trie length)
	 (cond
	   ((empty-clause-trie-p trie)
	    (incf nclauses)
	    (incf nliterals length))
	   (t
	    (loop for tp in trie
		  as subtries = (tp-subtries tp)
		  do (incf nnodes)
		     (let ((true-subtrie (true-subtrie subtries)))
		       (unless (empty-trie-p true-subtrie)
			 (trie-count* true-subtrie (1+ length))))
		     (let ((false-subtrie (false-subtrie subtries)))
		       (unless (empty-trie-p false-subtrie)
			 (trie-count* false-subtrie (1+ length)))))))))
      (trie-count* trie 0)
      (when print-p
	(format t "~&Trie contains ~D clauses with ~D literals using ~D nodes." nclauses nliterals nnodes))
      (values nclauses nliterals nnodes))))

(defun TRIE-CLAUSES (trie &optional decode-fun map-fun)
  ;; either return or apply map-fun to all clauses in trie
  (let (clauses)
    (labels
      ((trie-clauses* (trie decode-fun literals)
	 (cond
	   ((empty-clause-trie-p trie)
	    (if map-fun
		(funcall map-fun (reverse literals))
		(push (reverse literals) clauses)))
	   (t
	    (loop for tp in trie
		  as subtries = (tp-subtries tp)
		  do (let ((true-subtrie (true-subtrie subtries)))
		       (unless (empty-trie-p true-subtrie)
			 (trie-clauses* true-subtrie
					decode-fun
					(cons (if decode-fun
						  (funcall decode-fun (tp-atom tp))
						  (tp-atom tp))
					      literals))))
		     (let ((false-subtrie (false-subtrie subtries)))
		       (unless (empty-trie-p false-subtrie)
			 (trie-clauses* false-subtrie
					decode-fun
					(cons (if decode-fun
						  (list 'not (funcall decode-fun (tp-atom tp)))
						  (- (tp-atom tp)))
					      literals)))))))))
      (trie-clauses* trie decode-fun nil))
    (nreverse clauses)))

;;; trie-assign-atoms and trie-merge do the work of creating the new trie
;;; that results from assignming truth values to some atoms in a trie

(defmacro TRIE-MERGE (trie1 trie2)
  ;; check for empty and empty-clause tries without calling a function
  (cond
    ((not (symbolp trie1))
     (let ((x (gensym)))
       `(let ((,x ,trie1))
	  (trie-merge ,x ,trie2))))
    ((not (symbolp trie2))
     (let ((y (gensym)))
       `(let ((,y ,trie2))
	  (trie-merge ,trie1 ,y))))
    (t
     `(cond
	((empty-trie-p ,trie1)
	 ,trie2)
	((empty-trie-p ,trie2)
	 ,trie1)
	((or (empty-clause-trie-p ,trie1) (empty-clause-trie-p ,trie2))
	 t)
	(t
	 (trie-merge* ,trie1 ,trie2))))))

(defun TRIE-MERGE* (trie1 trie2)
  (loop with result = nil
	with result-tail
	as atom1 = (tp-atom (first trie1))
	as atom2 = (tp-atom (first trie2))
	as tp = (cond
		  ((<= (the fixnum atom1) (the fixnum atom2))
		   (cond
		     ((= (the fixnum atom1) (the fixnum atom2))
		      (let* ((v1 (tp-subtries (pop trie1)))
			     (v2 (tp-subtries (pop trie2)))
			     (w1 (trie-merge (true-subtrie v1) (true-subtrie v2)))
			     (w2 (trie-merge (false-subtrie v1) (false-subtrie v2))))
			(cond
			  ((and (empty-clause-trie-p w1) (empty-clause-trie-p w2))
			   (return-from trie-merge* t))
			  (t
			   (make-tp atom1 (cons w1 w2))))))
		     (t
		      (pop trie1))))
		  (t
		   (pop trie2)))
	do (if result
	       (setq result-tail (setf (cdr result-tail) (cons tp nil)))
	       (setq result (setq result-tail (cons tp nil))))
	when (null trie1)
	  do (setf (cdr result-tail) trie2)
	     (return result)
	when (null trie2)
	  do (setf (cdr result-tail) trie1)
	     (return result)))

(defmacro TRIE-ASSIGN-ATOMS (assignment trie)
  ;; check for empty and empty-clause tries without calling a function
  (cond
    ((not (symbolp trie))
     (let ((y (gensym)))
       `(let ((,y ,trie))
	  (trie-assign-atoms ,assignment ,y))))
    (t
     `(cond
	((or (empty-trie-p ,trie) (empty-clause-trie-p ,trie))
	 ,trie)
	(t
	 (trie-assign-atoms* ,assignment ,trie))))))

(defmacro TRIE-ASSIGN-ATOMS-VALUE (true-subtrie* false-subtrie* v)
  (let ((w (gensym)))
    `(let ((,w ,v))
       (cond
	 ((empty-clause-trie-p ,w)
	  t)
	 ((and (eq ,true-subtrie* true-subtrie)
	       (eq ,false-subtrie* false-subtrie))
	  (if (eq ,w (rest trie))
	      trie
	      (cons tp ,w)))
	 (t
	  (cons (make-tp atom (cons ,true-subtrie* ,false-subtrie*)) ,w))))))

(defun TRIE-ASSIGN-ATOMS* (assignment trie)
  ;; applies an assignment of truth values to atoms to a trie
  ;; assignment is list of dotted pairs of form (atom . truth-value)
  ;; where truth-value is :true or :false
  ;; and assignment is ordered by < on atoms
  (loop with tp = (first trie)
	with atom = (tp-atom tp)
	for l on assignment
	as assign = (first l)
	as x = (car assign)
	when (<= (the fixnum atom) (the fixnum x))
	  return (if (= (the fixnum atom) (the fixnum x))
		     (if (null (setq l (rest l)))
			 (trie-merge (let ((v (if (eq (cdr assign) :true)
						  (false-subtrie (tp-subtries tp))
						  (true-subtrie (tp-subtries tp)))))
				       (when v
					 (setq *pure* nil))
				       v)
				     (rest trie))
			 (trie-merge (trie-assign-atoms
				       l (if (eq (cdr assign) :true)
					     (false-subtrie (tp-subtries tp))
					     (true-subtrie (tp-subtries tp))))
				     (trie-assign-atoms
				       l (rest trie))))
		     (let ((w (trie-assign-atoms l (rest trie))))
		       (cond
			 ((empty-clause-trie-p w)
			  t)
			 (t
			  (let* ((subtries (tp-subtries tp))
				 (true-subtrie (true-subtrie subtries))
				 (false-subtrie (false-subtrie subtries))
				 (true-subtrie* (trie-assign-atoms l true-subtrie))
				 (false-subtrie* (trie-assign-atoms l false-subtrie)))
			    (cond
			      ((and (empty-trie-p true-subtrie*)
				    (empty-trie-p false-subtrie*))
			       w)
			      ((empty-clause-trie-p true-subtrie*)
			       (cond
				 ((empty-clause-trie-p false-subtrie*)
				  t)
				 (t
				  (trie-assign-atoms-value true-subtrie* nil (trie-merge false-subtrie* w)))))
			      ((empty-clause-trie-p false-subtrie*)
			       (trie-assign-atoms-value nil false-subtrie* (trie-merge true-subtrie* w)))
			      (t
			       (trie-assign-atoms-value true-subtrie* false-subtrie* w))))))))
	finally (return trie)))

(defun FIND-UNIT-CLAUSES-IN-TRIE (trie)
  (loop for tp in trie
	as subtries = (tp-subtries tp)
	when (empty-clause-trie-p (true-subtrie subtries))
	  if (empty-clause-trie-p (false-subtrie subtries)) do (return :unsatisfiable) else collect (cons (tp-atom tp) :true)
	when (empty-clause-trie-p (false-subtrie subtries))
	  collect (cons (tp-atom tp) :false)))

(defun LENGTH-OF-SHORTEST-POSITIVE-CLAUSE-IN-TRIE (trie &optional bound)
  ;; assume trie isn't t (just the empty clause) and bound if given is >= 2
  ;; only look for clauses strictly shorter than bound
  (cond
    ((loop for tp in trie			;look for positive unit clause
	   thereis (empty-clause-trie-p (true-subtrie (tp-subtries tp))))
     1)
    ((eql bound 2)
     nil)
    (t
     (loop with bound1 = (and bound (1- bound))
	   with best = nil
	   with m = nil
	   for tp in trie
	   as true-subtrie = (true-subtrie (tp-subtries tp))
	   unless (empty-trie-p true-subtrie)
	     do (cond
		  ((null (setq m (length-of-shortest-positive-clause-in-trie true-subtrie bound1)))
		   )
		  ((= m 1)
		   (return 2))
		  (t
		   (setq bound1 (setq best m))))
	   finally (return (and best (1+ best)))))))

(defun CHOOSE-AN-ATOM-OF-A-SHORTEST-POSITIVE-CLAUSE (trie)
  ;; assume trie isn't t (just the empty clause) and has no unit clauses
  ;; return the first literal of the first positive clause of minimum length
  (loop with atom = nil
	with best = nil
	with m = nil
	for tp in trie
	as true-subtrie = (true-subtrie (tp-subtries tp))
	unless (empty-trie-p true-subtrie)
	  do (cond
	       ((null (setq m (length-of-shortest-positive-clause-in-trie true-subtrie best)))
		)
	       ((= m 1)				;quit at first 2-clause
		(return (values (tp-atom tp) :true :false)))
	       (t
		(setq best m)
		(setq atom (tp-atom tp))))
	finally (return (if atom (values atom :true :false) :satisfiable))))

;;; Examples.
;;; Clauses are represented by lists of literals.
;;; Atomic formulas are represented by numbers > 0.
;;; For example, 3 is a positive literal and -3 is its negation.
;;; Clauses are added to a set of clauses by dp-insert.
;;; Literals in clauses are required to be ordered by < on absolute value
;;; dp-insert-sorted can be used instead of dp-insert to sort and add clauses.
;;; Tautologies and duplicate literals are automatically eliminated.

(defun ALLWAYS-3-PROBLEM ()
  ;; all signed combinations of three propositions
  ;; this is not satisfiable
  ;; you can omit some of the clauses to make the set
  ;; satisfiable and observe dp-satisfiable-p's behavior
  (let ((clause-set nil))
    (setq clause-set (dp-insert '(1 2 3) clause-set))
    (setq clause-set (dp-insert '(1 2 -3) clause-set))
    (setq clause-set (dp-insert '(1 -2 3) clause-set))
    (setq clause-set (dp-insert '(1 -2 -3) clause-set))
    (setq clause-set (dp-insert '(-1 2 3) clause-set))
    (setq clause-set (dp-insert '(-1 2 -3) clause-set))
    (setq clause-set (dp-insert '(-1 -2 3) clause-set))
    (setq clause-set (dp-insert '(-1 -2 -3) clause-set))
;;  (dp-count clause-set t)
;;  (mapc #'print (dp-clauses clause-set))
    (dp-satisfiable-p clause-set)))

(defun PIGEONHOLE-PROBLEM (nobjects)
  (dp-satisfiable-p (pigeonhole-problem-clauses nobjects)))

(defun QUEENS-PROBLEM (n &optional find-all-models)
  (dp-satisfiable-p (queens-problem-clauses n) find-all-models))

(defun PIGEONHOLE-PROBLEM-CLAUSES (nobjects)
  (let ((nholes (1- nobjects)) (clause-set nil))
    (loop for i from 1 to nobjects
	  do (setq clause-set (dp-insert (loop for j from 1 to nholes collect (encode-example-atom nobjects i j))
					 clause-set)))
    (loop for j from 1 to nholes
	  do (loop for i1 from 1 to (1- nobjects)
		   do (loop for i2 from (1+ i1) to nobjects
			    do (setq clause-set (dp-insert (list (- (encode-example-atom nobjects i1 j))
								 (- (encode-example-atom nobjects i2 j)))
							   clause-set)))))
    clause-set))

(defun QUEENS-PROBLEM-CLAUSES (n)
  (let ((clause-set nil))
    (loop for i from 1 upto n
	  do (setq clause-set (dp-insert (loop for j from 1 upto n collect (encode-example-atom n i j))
					 clause-set)))
    (loop for i from 1 upto n
	  do (loop for j from 1 upto (1- n)
		   do (loop for k from (1+ j) upto n
			    do (setq clause-set (dp-insert (list (- (encode-example-atom n i j))
								 (- (encode-example-atom n i k)))
							   clause-set))
			       (setq clause-set (dp-insert (list (- (encode-example-atom n j i))
								 (- (encode-example-atom n k i)))
							   clause-set)))))
    (loop for i1 from 1 upto (1- n)
	  do (loop for i2 from (1+ i1) upto n
		   as d = (- i2 i1)
		   do (loop for j1 from 1 upto n
			    when (>= (- j1 d) 1)
			      do (setq clause-set (dp-insert-sorted (list (- (encode-example-atom n i1 j1))
									  (- (encode-example-atom n i2 (- j1 d))))
								    clause-set))
			    when (<= (+ j1 d) n)
			      do (setq clause-set (dp-insert-sorted (list (- (encode-example-atom n i1 j1))
									  (- (encode-example-atom n i2 (+ j1 d))))
								    clause-set)))))
    clause-set))

(defun ENCODE-EXAMPLE-ATOM (n i j)
  (+ n (* 1000 (+ (* (1- i) n) (1- j)))))

(defun DECODE-EXAMPLE-ATOM (v)
  (let ((n (mod v 1000)) i j)
    (setq v (floor v 1000))
    (setq j (1+ (mod v n)))
    (setq v (floor v n))
    (setq i (1+ v))
    (values `(p ,i ,j) n)))


(defun myassert (arg)
  (assert arg)
)

